
module ALU (
  input [3:0] alu_op_in,
  input [3:0] mdu_op_in,
  input [63:0] a_in,
  input [63:0] b_in,
  output reg [63:0] alu_out,
  output [3:0] flag_out
);

  wire negtive, zero, carry, overflow;
  assign flag_out = {negtive, zero, carry, overflow};
  assign negtive = alu_out[63];
  assign zero = ~(|alu_out);
  assign overflow = (addr_a_in[63] & addr_b_in[63] & ~addr_out[63]) | (~addr_a_in[63] & ~addr_b_in[63] & addr_out[63]);

  wire [63:0] a_in_w, b_in_w;
  assign a_in_w = {{32{a_in[31]}}, a_in[31:0]};
  assign b_in_w = {{32{b_in[31]}}, b_in[31:0]};

  //addr
  wire [63:0] addr_out, addr_w_out;
  wire [63:0] addr_a_in, addr_b_in;
  wire addr_c_in;

  MuxKeyWithDefault #(4, 4, 64) addr_a_in_mux (addr_a_in, alu_op_in, a_in, {
    `ALU_OP_ADD, a_in,
    `ALU_OP_SUB, a_in,
    `ALU_OP_ADDW, a_in_w,
    `ALU_OP_SUBW, a_in_w
  });
  MuxKeyWithDefault #(6, 4, 64) addr_b_in_mux (addr_b_in, alu_op_in, b_in, {
    `ALU_OP_ADD, b_in,
    `ALU_OP_SUB, ~b_in,
    `ALU_OP_ADDW, b_in_w,
    `ALU_OP_SUBW, ~b_in_w,
    `ALU_OP_SLT, ~b_in_w,
    `ALU_OP_SLTU, ~b_in_w
  });
  MuxKeyWithDefault #(6, 4, 1) addr_c_in_mux (addr_c_in, alu_op_in, 1'b0, {
    `ALU_OP_ADD, 1'b0,
    `ALU_OP_SUB, 1'b1,
    `ALU_OP_ADDW, 1'b0,
    `ALU_OP_SUBW, 1'b1,
    `ALU_OP_SLT, 1'b1,
    `ALU_OP_SLTU, 1'b1
  });
  addr_64_bits addr_64(addr_a_in, addr_b_in, addr_c_in, addr_out, carry, , );
  assign addr_w_out = {{32{addr_out[31]}}, addr_out[31:0]};


  wire [63:0] xor_out, and_out, or_out;
  assign xor_out = a_in ^ b_in;
  assign and_out = a_in & b_in;
  assign or_out  = a_in | b_in;


  wire [63:0] shift_out, shift_32_out;
  wire [31:0] shift_32_temp;
  wire [1:0] shift_op;
  MuxKeyWithDefault #(6, 4, 2) shift_mux (shift_op, alu_op_in, 2'b00, {
    `ALU_OP_SRL, 2'b10,
    `ALU_OP_SRA, 2'b11,
    `ALU_OP_SLL, 2'b00,
    `ALU_OP_SRLW, 2'b10,
    `ALU_OP_SRAW, 2'b11,
    `ALU_OP_SLLW, 2'b00
  });
  barrel_shift shift_64 (a_in, b_in[5:0], shift_op, shift_out);
  barrel_shift #(32, 5) shift_32 (a_in[31:0], b_in[4:0], shift_op, shift_32_temp);
  assign shift_32_out = {{32{shift_32_temp[31]}}, shift_32_temp};

  wire [63:0] mdu_out, alu_temp_out;
  MDU mdu_64 (
    .a_in(a_in),
    .b_in(b_in),
    .mdu_op_in(mdu_op_in),
    .mdu_out(mdu_out),
    .overflow_out()
  );

  MuxKeyWithDefault #(16, 4, 64) out_mux ( alu_temp_out, alu_op_in, addr_out, {
    `ALU_OP_ADD, addr_out,
    `ALU_OP_SUB, addr_out,
    `ALU_OP_SRL, shift_out,
    `ALU_OP_SRA, shift_out,
    `ALU_OP_SLL, shift_out,
    `ALU_OP_XOR, xor_out,
    `ALU_OP_OR,  or_out,
    `ALU_OP_AND, and_out,
    `ALU_OP_ADDW, addr_w_out,
    `ALU_OP_SUBW, addr_w_out,
    `ALU_OP_SRLW, shift_32_out,
    `ALU_OP_SRAW, shift_32_out,
    `ALU_OP_SLLW, shift_32_out,
    `ALU_OP_SLT,  {63'b0, overflow ^ addr_out[63]},
    `ALU_OP_SLTU, {63'b0, ~carry},
    `ALU_OP_BOUT, b_in
  });

  assign alu_out = mdu_op_in == 4'b0 ? alu_temp_out : mdu_out;

endmodule

module barrel_shift #(DATA_LEN = 64, SHIFT_LEN = 6) (
  input [DATA_LEN-1:0] data_in,
  input [SHIFT_LEN-1:0] shift_amount_in,
  input [1:0] shift_op_in,
  output [DATA_LEN-1:0] data_out
);
  wire [DATA_LEN-1:0] shift_left, shift_rignt_logical, shift_right_arithmetic;
  wire signed [DATA_LEN-1:0] signed_data;
  assign signed_data = data_in;

  assign shift_left = data_in << shift_amount_in;
  assign shift_rignt_logical = data_in >> shift_amount_in;
  assign shift_right_arithmetic = signed_data >>> shift_amount_in;
  MuxKeyWithDefault #(3, 2, DATA_LEN) out_mux (data_out, shift_op_in, shift_left, {
    2'b00, shift_left,
    2'b10, shift_rignt_logical,
    2'b11, shift_right_arithmetic
  });
endmodule


module addr_64_bits (
  input [63:0] a,
  input [63:0] b,
  input cin,
  output [63:0] out,
  output cout,
  output P,
  output G
);
  wire p0,p1,p2,p3,g0,g1,g2,g3,c0,c1,c2,c3;
  addr_16_bits addr0(a[15:0], b[15:0], cin, out[15:0], ,p0, g0);
  addr_16_bits addr1(a[31:16], b[31:16], c0, out[31:16], ,p1, g1);
  addr_16_bits addr2(a[47:32], b[47:32], c1, out[47:32], ,p2, g2);
  addr_16_bits addr3(a[63:48], b[63:48], c2, out[63:48], cout, p3, g3);
  carry_look_ahead_4_bits cla (p0,p1,p2,p3,g0,g1,g2,g3,cin,P,G,c0,c1,c2,c3);
endmodule

module addr_16_bits (
  input [15:0] a,
  input [15:0] b,
  input cin,
  output [15:0] out,
  output cout,
  output P,
  output G
);
  wire p0,p1,p2,p3,g0,g1,g2,g3,c0,c1,c2,c3;
  addr_4_bits addr0(a[3:0], b[3:0], cin, out[3:0], ,p0,g0);
  addr_4_bits addr1(a[7:4], b[7:4], c0, out[7:4], ,p1,g1);
  addr_4_bits addr2(a[11:8], b[11:8], c1, out[11:8], ,p2,g2);
  addr_4_bits addr3(a[15:12], b[15:12], c2, out[15:12], cout, p3,g3);
  carry_look_ahead_4_bits cla (p0,p1,p2,p3,g0,g1,g2,g3,cin,P,G,c0,c1,c2,c3);
endmodule

module addr_4_bits (
  input [3:0] a,
  input [3:0] b,
  input cin,
  output [3:0] out,
  output cout,
  output P,
  output G
);
  wire p0,p1,p2,p3,g0,g1,g2,g3,c0,c1,c2,c3;
  assign p0 = a[0] ^ b[0];
  assign p1 = a[1] ^ b[1];
  assign p2 = a[2] ^ b[2];
  assign p3 = a[3] ^ b[3];
  assign g0 = a[0] & b[0];
  assign g1 = a[1] & b[1];
  assign g2 = a[2] & b[2];
  assign g3 = a[3] & b[3];

  addr addr0(a[0], b[0], cin, out[0], );
  addr addr1(a[1], b[1], c0, out[1], );
  addr addr2(a[2], b[2], c1, out[2], );
  addr addr3(a[3], b[3], c2, out[3], cout);
  carry_look_ahead_4_bits cla (p0,p1,p2,p3,g0,g1,g2,g3,cin,P,G,c0,c1,c2,c3);
endmodule

module addr (
  input a,
  input b,
  input cin,
  output out,
  output cout
);
  assign out = a ^ b ^ cin;
  assign cout = a & cin | b & cin | a & b;
endmodule

module carry_look_ahead_4_bits (
  input p0,
  input p1,
  input p2,
  input p3,
  input g0,
  input g1,
  input g2,
  input g3,
  input cin,
  output P,
  output G,
  output c0,
  output c1,
  output c2,
  output c3
);
  assign c0 = g0 | p0 & cin;
  assign c1 = g1 | p1 & c0;
  assign c2 = g2 | p2 & c1;
  assign c3 = g3 | p3 & c2;

  assign P = p0 & p1 & p2 & p3;
  assign G = g3 | p3 & g2 | p3 & p2 & g1 | p3 & p2 & p1 & g0;
endmodule